/*
 * (C) Copyright 2005 Davide Brugali, Marco Torchiano
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 * 02111-1307  USA
 */
package multiformat;

/**
 * This class represents the logic behind the calculator.
 * 
 * @author Alexander Jeurissen, Dustin Meijer
 */

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.ListIterator;
import java.util.Stack;



/**
 * The multiformat calculator
 */
public class Calculator {
  
	private Stack<Rational> operands;
	private StringBuffer  history;
	
	private static final int FIRST_INDEX = 0;
	
	//operations
	private static final String PLUS = "+";
	private static final String MIN = "-";
	private static final String MUL = "*";
	private static final String DIV = "/";
	
	//access identifiers
	private static final int POP = 0;
	private static final int PEEK = 1;
	private static final int PUSH = 2;
	private static final int DEL = 3;
	private static final int SET = 4;
	private static final int GET = 5;
	
	// The current format of the calculator
	private Format format;
	// The current numberbase of the calculator
	private Base base;
	// The list with ActionListeners
	private ArrayList<ActionListener> actionListenerList;
  

	/**
	 * The Default constructor for the Calculator. 
	 * This will initialize everything so the object is ready for use.
	 */
	public Calculator(){
	  
		
		this.format = new FixedPointFormat();
		this.base = new DecimalBase();
		this.operands = new Stack<Rational>();
		this.history = new StringBuffer();
		this.actionListenerList = new ArrayList<ActionListener>();
	  
	}
  
	/**
	 * This method will be called if a new operand needs to be added. If the operand
	 * is outside the range of the current number base, a NumberBaseException will be thrown.
	 * If the operand is not in the proper format, a FormatException will be thrown.	 * 
	 * 
	 * @param String newOperand
	 * @throws FormatException
	 * @throws NumberBaseException
	 */
	public void addOperand(String newOperand) throws FormatException, NumberBaseException {
 
      AccessOperand( PUSH, format.parse(newOperand, base), 0 );
      
      addToHistory(SET,format.parse(newOperand,base));
      processEvent( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, null));
      
      
	}
	
	/**
	 * This method will add the two last added operands and will save the result. 
	 */
	public void add(){

		Rational firstOperand = AccessOperand(POP,null, 0);
		Rational secondOperand = AccessOperand(POP,null, 0);
		
		AccessOperand( PUSH, secondOperand.plus(firstOperand), 0 );
		
		addOperationToHistory(firstOperand,secondOperand,PLUS);
		processEvent( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, null));
		
	}
	
	/**
	 * This method will subtract the last two added operands and save the result.
	 */
	public void subtract() {
	  
		Rational firstOperand = AccessOperand(POP,null, 0);
		Rational secondOperand = AccessOperand(POP,null, 0);
		
		AccessOperand(PUSH, secondOperand.minus(firstOperand), 0 );
		
		addOperationToHistory(firstOperand,secondOperand,MIN);
		
		processEvent( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, null));
	}
  
	/**
	 * This method will multiply the last two added operands and save the result.
	 */
	public void multiply() {
		
		Rational firstOperand = AccessOperand(POP,null, 0);
		Rational secondOperand = AccessOperand(POP,null, 0);
		
		AccessOperand(PUSH, secondOperand.mul(firstOperand) , 0);	
		
		addOperationToHistory(firstOperand,secondOperand,MUL);
			  
		processEvent( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, null));
	}
  
	/**
	 * This method will divide the last two added operands and save the result.
	 */
	public void divide() {
	    
		Rational firstOperand = AccessOperand(POP,null, 0);
		Rational secondOperand = AccessOperand(POP,null, 0);
		Rational result = secondOperand.div(firstOperand);
		
		AccessOperand(PUSH, result , 0);
		
		if(!result.isZero()){
			addOperationToHistory(secondOperand,firstOperand,DIV);
		}
				 
		processEvent( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, null));
	}
  
	/**
	 * This method will delete the last added operand. If there are no operands,
	 * a call to this method will do nothing.
	 */
	public void delete() {
		
		addToHistory(DEL,AccessOperand(PEEK,null, 0));
		AccessOperand(DEL,null, 0);
	
		processEvent( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, null));
	}

	/**
	 * This method will return the last added operand. If no operand is available,
	 * a string representing zero will be returned.
	 * 
	 * @return String operand
	 */
	public String firstOperand(){
		if(operands.size() == 0){
			return format.toString(new Rational(), base);
		}
		return format.toString(AccessOperand(GET,null,0),base);
	}
  
	/**
	 * This method will return the second last added operand. If no operand is available,
	 * a string representing zero will be returned.
	 * 
	 * @return String operand
	 */
	public String secondOperand(){
		if(operands.size() <= 1){
			return format.toString(new Rational(), base);
		}
		return format.toString(AccessOperand(GET,null,1),base);
	}
	
	/**
	 * This method will return a String with all the recorded operations.
	 * 
	 * @return String history
	 */
	public String getHistory() {
		return history.toString();
	}
	
	public String getStackContent() {
		StringBuffer result = new StringBuffer();
		//if(operands.size() > 0){
			for(ListIterator<Rational> it = operands.listIterator(operands.size());it.hasPrevious();){
				result.append("operand "+(it.previousIndex()+1) + " : " + format.toString(it.previous(), base) + "\n" );
			}
			
			return result.toString();
		//}else{
		//	return null;
		//}
	}

	/**
	 * This method will set the base to the new base that is provided.
	 * 
	 * @param Base newBase
	 */
	public void setBase(Base newBase){
		base = newBase;
		processEvent( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, null));
	}
  
	/**
	 * This method will return a reference to the current Base.
	 * 
	 * @return Base base
	 */
	public Base getBase(){
		return base;
	}
  
	/**
	 * This method will set the format to the new format that is provided.
	 * 
	 * @param Format newFormat
	 */
	public void setFormat(Format newFormat){
		format = newFormat;
		processEvent( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, null));
	}
  
	/**
	 * This method will return a reference to the current format.
	 * 
	 * @return Format format
	 */
	public Format getFormat(){
		return format;
	}
	
	/**
	 * This method will enable the specified view.
	 */
	public void showView(String view) {
		processEvent( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, view));
	}
	
	/**
	 * This method allows other objects to register as actionlisteners.
	 * 
	 * @param ActionListener listener
	 */
	public void addActionListener( ActionListener listener){
		actionListenerList.add( listener );
	}
	
	/**
	 * This method will remove the given ActionListener from the registered ActionListeners.
	 * 
	 * @param ActionListener listener
	 */
	public void removeActionListener( ActionListener listener){
		if ( actionListenerList.contains( listener ) ){
			actionListenerList.remove( listener );
		}
	}
	
	/**
	 * This method is a all around way of accessing the storage of the operands. Depending on
	 * what operation int is provided, it will execute different code. The supported operations are recorded
	 * in the class as static final ints.
	 * 
	 * Operation POP  : Will return the last added operand as a Rational.
	 * Operation PEEK : Will return the last added operand as a Rational without removing it.
	 * Operation PUSH : Will add the given Rational operand as to the storage as the last added. Returns null.
	 * Operation DEL  : Will delete the last added operand, if no operand is available, nothing happens. Returns null.
	 * 
	 * @param int operation
	 * @param Rational operand
	 * @return Rational operand
	 */
	private Rational AccessOperand(int operation, Rational operand, int index) {
		switch(operation) {
			
		case POP:
			//pop
			if(operands.size() < 1) {
				return new Rational();
			}else {
				return operands.pop();
			}
		
		case PEEK:
			//peek
			if(operands.size() < 1) {
				return new Rational();
			}else{
				return operands.peek();
			}
		
		case PUSH:
			operands.push(operand);
			return null;
		
		case DEL:
			if(operands.size() > 0) {
				operands.remove(FIRST_INDEX);
			}
			return null;
		case GET:
			if(operands.size() > index){
				return operands.get(index);
			}
			return null;
			
		default:
			throw new IllegalArgumentException("Calculator: Wrong argument supplied.");
		
		}
		
		
	}
	

	/**
	 * This method will add a operation to the history buffer. It requires the two operands and the operation 
	 * it under went. This method will also retrieve the result to complete the history. 
	 * 
	 * @param Rational first
	 * @param Rational second
	 * @param String operation
	 */
	private void addOperationToHistory(Rational first, Rational second, String operation) {
		String firstop = format.toString(first, base);
		String secondop = format.toString(second, base);
		String result = format.toString(operands.peek(),base);
		history.append((firstop+" "+operation+" "+secondop+" = " + result) + "\n");
	}
	
	/**
	 * This method is intended to provide a way to add simple operations to the history via pre-recorded
	 * possible operations. The possible operations are stored as static final ints in Calculator. 
	 * 
	 * Operation SET : This will add a message that a operand has been added.
	 * Operation DEL : This will add a message that a operand has been deleted.
	 * 
	 * @param int operation
	 * @param Rational operand
	 */
	private void addToHistory(int operation, Rational operand) {
		String op;
		
		switch(operation) {
		case SET:
			 op = format.toString(operand,base);
			 history.append("Added operand : "+op + "\n");
		break;
		
		case DEL:
			 op = format.toString(operand,base);
			 history.append("Removed operand : "+op + "\n");
		break;
			
			
		}
		
	}	

	/**
	 * This method is intended to notify all actionlisteners of a event.
	 * 
	 * @param ActionEvent event
	 */
	private void processEvent(ActionEvent event){
	 
		for( ActionListener listener : actionListenerList){
			listener.actionPerformed( event );
		}
	}
}